package openglsuperbible.Chapter5;

import de.matthiasmann.twl.utils.PNGDecoder;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import openglsuperbible.glutils.*;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL12.*;
import static org.lwjgl.opengl.GL30.*;

/**
 *
 * @author Matthew
 */
public class SphereWorldTextured {
        public static final int DISPLAY_HEIGHT = 600;
        public static final int DISPLAY_WIDTH = 800;
        private static final int NUM_SPHERES = 50;
        private GLShader shaderFLAT; 
        private GLShader shaderTEXTURE_MODULATE;
        private GLShader shaderTEXTURE_POINT_LIGHT;
        private MatrixStack modelViewMatrix;  
        private MatrixStack projectionMatrix; 
        private GLFrustrum frustrum;   
        private GeometryTransform transformPipeline; 
        private GLFrame cameraFrame;
        
        private GLBatch torusBatch;        
        private GLBatch sphereBatch;
        private GLBatch floorBatch;
        private GLFrame[] spheres;
        private int[] textureID;

        /** time at last frame */
	long lastFrame;
        
        float vWhite[] = { 1.0f, 1.0f, 1.0f, 1.0f };
	float vLightPos[] = { 0.0f, 3.0f, 0.0f, 1.0f };
        float vFloorColor[] = { 1.0f, 1.0f, 1.0f, 0.75f};
        
         public void initGL() {  
             shaderFLAT = GLShaderFactory.getFlatShader();
             shaderTEXTURE_POINT_LIGHT = GLShaderFactory.getTexturePointLightDiffuseShader();
             shaderTEXTURE_MODULATE = GLShaderFactory.getTextureModulateShader();
             glEnable(GL_DEPTH_TEST);
             glEnable(GL_CULL_FACE);	
             glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
             
             frustrum = new GLFrustrum();               
             modelViewMatrix = new MatrixStack();
             cameraFrame = new GLFrame();
             
             torusBatch = GLBatchFactory.makeTorus(0.4f, 0.15f, 40, 20);
             sphereBatch = GLBatchFactory.makeSphere(0.1f, 26, 13);
             
             textureID = new int[3];
             
             textureID[0] =  loadTexture("Marble",GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT); 
             textureID[1] =  loadTexture("Marslike",GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE); 
             textureID[2] =  loadTexture("MoonLike",GL_LINEAR_MIPMAP_LINEAR,  GL_LINEAR, GL_CLAMP_TO_EDGE);
                         
            
            GLBatchFactory batch = new GLBatchFactory();
            float texSize = 10.0f;
            
            float[] normals = {0,1,0};
            batch.addVertex(new float[]{-20.0f, -0.41f, 20.0f},normals, new float[]{0.0f, 0.0f});
            batch.addVertex(new float[]{20.0f, -0.41f, 20.0f}, normals, new float[]{texSize, 0.0f});
            batch.addVertex(new float[]{20.0f, -0.41f, -20.0f}, normals, new float[]{texSize, texSize});
            batch.addVertex(new float[]{-20.0f, -0.41f, -20.0f}, normals, new float[]{0.0f, texSize});

            floorBatch = batch.buildBatch(GL_TRIANGLE_FAN);
             
              //                               
            spheres = new GLFrame[NUM_SPHERES];
    //                
    //      // Randomly place the spheres
            for(int i = 0; i < NUM_SPHERES; i++) {
                    float x = ((float)((Math.random() * 400) - 200) * 0.1f);
                    float z = ((float)((Math.random() * 400) - 200) * 0.1f);
                    float y = ((float)((Math.random() * 20)) * 0.1f);
                    spheres[i] = new GLFrame();                    
                    spheres[i].setOrigin(x, 0.0f, z); 
                }
             
         }
         
         
         
         
        void drawSongAndDance(float yRot)		// Called to draw dancing objects
	{

            float[] mCamera = cameraFrame.getCameraMatrix();            
            float[] vLightEyePos = Math3D.transformVector4( vLightPos, mCamera);

            // Draw the light source
            modelViewMatrix.push();
            modelViewMatrix.translate(vLightPos[0], vLightPos[1], vLightPos[2]);
             
            shaderFLAT.useShader();
            shaderFLAT.setUniform4("vColor",vWhite); 

            shaderFLAT.setUniformMatrix4("mvpMatrix", false, transformPipeline.getModelViewProjectionMatrix()); 
            sphereBatch.draw(shaderFLAT.getAttributeLocations());
            modelViewMatrix.pop();
            
            
            shaderTEXTURE_POINT_LIGHT.useShader();
            glBindTexture(GL_TEXTURE_2D, textureID[2]);
            for(int i = 0; i < NUM_SPHERES; i++) {
                modelViewMatrix.push();
                modelViewMatrix.multiply4(spheres[i].getMatrixf());
                
                shaderTEXTURE_POINT_LIGHT.setUniformMatrix4("pMatrix", false, transformPipeline.getProjectionMatrix());
                shaderTEXTURE_POINT_LIGHT.setUniformMatrix4("mvMatrix", false, transformPipeline.getModelViewMatrix());
                shaderTEXTURE_POINT_LIGHT.setUniform4("vColor", vWhite);
                shaderTEXTURE_POINT_LIGHT.setUniform3("vLightPos",vLightEyePos);
                shaderTEXTURE_POINT_LIGHT.setUniform1i("textureUnit0",0);
                sphereBatch.draw(shaderTEXTURE_POINT_LIGHT.getAttributeLocations());
                modelViewMatrix.pop();
            }

            // Song and dance
            modelViewMatrix.translate(0.0f, 0.2f, -2.5f);
            modelViewMatrix.push();	// Saves the translated origin
            modelViewMatrix.rotate(yRot, 0.0f, 1.0f, 0.0f);

            // Draw stuff relative to the camera
            glBindTexture(GL_TEXTURE_2D, textureID[1]);
            shaderTEXTURE_POINT_LIGHT.setUniformMatrix4("pMatrix", false, transformPipeline.getProjectionMatrix());
            shaderTEXTURE_POINT_LIGHT.setUniformMatrix4("mvMatrix", false, transformPipeline.getModelViewMatrix());
            shaderTEXTURE_POINT_LIGHT.setUniform4("vColor", vWhite);
            shaderTEXTURE_POINT_LIGHT.setUniform3("vLightPos",vLightEyePos); 
            shaderTEXTURE_POINT_LIGHT.setUniform1i("textureUnit0",0);
            torusBatch.draw(shaderTEXTURE_POINT_LIGHT.getAttributeLocations());
            modelViewMatrix.pop(); // Erased the rotate

            modelViewMatrix.rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
            modelViewMatrix.translate(0.8f, 0.0f, 0.0f);

            glBindTexture(GL_TEXTURE_2D, textureID[2]);
            shaderTEXTURE_POINT_LIGHT.setUniformMatrix4("pMatrix", false, transformPipeline.getProjectionMatrix());
            shaderTEXTURE_POINT_LIGHT.setUniformMatrix4("mvMatrix", false, transformPipeline.getModelViewMatrix());
            shaderTEXTURE_POINT_LIGHT.setUniform4("vColor", vWhite);
            shaderTEXTURE_POINT_LIGHT.setUniform3("vLightPos",vLightEyePos);
            shaderTEXTURE_POINT_LIGHT.setUniform1i("textureUnit0",0);
            sphereBatch.draw(shaderTEXTURE_POINT_LIGHT.getAttributeLocations());
           

	}


   float yRot = 0.0f;
   
    void render(int delta)
    {
	yRot += 0.05f * delta;	
	
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
        modelViewMatrix.push();
        float[] mCamera = cameraFrame.getCameraMatrix();
        modelViewMatrix.multiply4(mCamera);
        
        
	// Draw the world upside down
	modelViewMatrix.push();	
	modelViewMatrix.scale(1.0f, -1.0f, 1.0f); // Flips the Y Axis
	modelViewMatrix.translate(0.0f, 0.8f, 0.0f); // Scootch the world down a bit...
	glFrontFace(GL_CW);
	drawSongAndDance(yRot);
	glFrontFace(GL_CCW);
	modelViewMatrix.pop();
	
	// Draw the solid ground
	glEnable(GL_BLEND);
	glBindTexture(GL_TEXTURE_2D, textureID[0]);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        
        shaderTEXTURE_MODULATE.useShader();
	shaderTEXTURE_MODULATE.setUniformMatrix4("mvpMatrix", false, transformPipeline.getModelViewProjectionMatrix()); 
        shaderTEXTURE_MODULATE.setUniform4("vColor",vFloorColor); 
        shaderTEXTURE_MODULATE.setUniform1i("textureUnit0",0);
							
	floorBatch.draw(shaderTEXTURE_MODULATE.getAttributeLocations());
 
	glDisable(GL_BLEND);
	
	
	drawSongAndDance(yRot);
	modelViewMatrix.pop();
        modelViewMatrix.pop();
        Display.update();

    }         
         
        public void resizeGL() {   
            glViewport(0,0,DISPLAY_WIDTH ,DISPLAY_HEIGHT);
            frustrum.setPerspective(35f, Display.getWidth()/ Display.getHeight(),1.0f, 100.0f);
            projectionMatrix = new MatrixStack(frustrum.getProjectionMatrix());
            // Set the transformation pipeline to use the two matrix stacks 
            transformPipeline = new GeometryTransform(modelViewMatrix, projectionMatrix);
        } 
        
        
         public void pollInput()
        {
            
            float linear = 0.1f;
            float angular = (float)Math.toRadians(1.0f);

            if (Keyboard.isKeyDown(Keyboard.KEY_UP)) {
                    cameraFrame.moveForward(linear);
                    
            }
            if (Keyboard.isKeyDown(Keyboard.KEY_DOWN)){
                    cameraFrame.moveForward(-linear);
                   
            }
            if (Keyboard.isKeyDown(Keyboard.KEY_LEFT)){
                    cameraFrame.rotateWorld(angular,0,1,0);

            }
            if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT)){
                    cameraFrame.rotateWorld(-angular,0,1,0);

            }
        }
         
          public void create() throws LWJGLException { 
            //Display    
            Display.setDisplayModeAndFullscreen(new DisplayMode(DISPLAY_WIDTH,DISPLAY_HEIGHT)); 
            
            Display.setTitle("SphereWorldTextured");
            Display.create();
            
            //Keyboard         
            Keyboard.create();   
            //Mouse          
            Mouse.setGrabbed(false);
            Mouse.create();  
            //OpenGL      
            initGL();    
            resizeGL();  
        }  
       
       
            public void destroy() {  
                //Methods already check if created before destroying.  
                Mouse.destroy();   
                Keyboard.destroy();
                Display.destroy();     
            } 
        
            public static void main(String[] args) {
            SphereWorldTextured main = null;
            try { 
                 main = new SphereWorldTextured(); 
                main.create(); 
                main.run();
            }          
            catch(Exception ex) {  
                 
            }          
            finally { 
                if(main != null) {  
                    main.destroy();
                }         
            }      
        } 
            
            public void run() 
            {
                while (!Display.isCloseRequested() && !Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {              
                       
                render(getDelta()); 
                pollInput();
                Display.sync(60);
                Display.setVSyncEnabled(true);
                }
            }
                        
                            	/** 
	 * Calculate how many milliseconds have passed 
	 * since last frame.
	 * 
	 * @return milliseconds passed since last frame 
	 */
	public int getDelta() {
	    long time = getTime();
	    int delta = (int) (time - lastFrame);
	    lastFrame = time;
	 
	    return delta;
	}
	
	/**
	 * Get the accurate system time
	 * 
	 * @return The system time in milliseconds
	 */
	public long getTime() {
	    return (Sys.getTime() * 1000) / Sys.getTimerResolution();
	}
        
      private int loadTexture(String filename, int minFilter, int magFilter, int wrapMode){
            
            InputStream in;
            int texID = glGenTextures();
            try {
                in = new FileInputStream("textures/" + filename + ".png");
                PNGDecoder decoder = new PNGDecoder(in);
                int width = decoder.getWidth();
                int height = decoder.getHeight();
                
                ByteBuffer buffer = BufferUtils.createByteBuffer(4 * width * height);
                decoder.decode(buffer, width * 4, PNGDecoder.Format.RGBA);          
                buffer.flip();
                in.close();
                
                glBindTexture(GL_TEXTURE_2D, texID);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
                
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
                
                glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
                glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

//                             
                if(minFilter == GL_LINEAR_MIPMAP_LINEAR || 
                    minFilter == GL_LINEAR_MIPMAP_NEAREST ||
                     minFilter == GL_NEAREST_MIPMAP_LINEAR ||
                     minFilter == GL_NEAREST_MIPMAP_NEAREST){
                glGenerateMipmap(GL_TEXTURE_2D);}       
                glBindTexture(GL_TEXTURE_2D, 0);
                
            } catch (FileNotFoundException e) {
                System.err.println("Failed to find the texture file: " + filename + ".png");
                
            } catch (IOException ex) {
                System.err.println("Error reading file: " + filename + ".png");
                
            }            
            return texID;
        }
        
  
}
